﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Bymed.SpineScan.Repository.Interface;

namespace Bymed.SpineScan.Repository.EntityFramework
{
    /// <summary>
    /// An instance of IRepository against the specific DataModel type and provide standard CRUD operation only.
    /// </summary>
    /// <typeparam name="T">The specific DataModel type.</typeparam>
    public class RepositoryBase<T> : IRepository<T> where T : class
    {
        protected SpineScanContext DataContext;

        /// <summary>
        /// Just need to make sure every sub-class has a contructor accpets ISession parameter.
        /// </summary>
        /// <param name="session">An instance implements ISession.</param>
        public RepositoryBase(SpineScanContext dataContext)
        {
            DataContext = dataContext;
        }

        /// <summary>
        /// Add new entity.
        /// </summary>
        /// <param name="entity">The new entity will be added into database.</param>
        /// <returns>The id of the new added entity.</returns>
        public virtual T Add(T entity)
        {
            entity = DataContext.Set<T>().Add(entity);
            return entity;
        }

        /// <summary>
        /// Add new entity list.
        /// </summary>
        /// <param name="entities">The new entity list will be added into database.</param>
        /// <returns>The new added entity list.</returns>
        public virtual IEnumerable<T> Add(IEnumerable<T> entities)
        {
            foreach (var entity in entities)
            {
                Add(entity);
            }

            return entities;
        }

        // <summary>
        /// Get entity by id.
        /// </summary>
        /// <param name="id">The specified id.</param>
        /// <returns>The matched data.</returns>
        public virtual T Get(object id)
        {
            return DataContext.Set<T>().Find(id);
        }

        /// <summary>
        /// Get entity by id. It will never return null. It will always return an entity or throw an exception.
        /// It is free to return a proxy instead. If you know that the value exist in the database, 
        /// and you don’t want to pay the extra select to have that, 
        /// but you want to get that value so we can add that reference to an object.
        /// </summary>
        /// <param name="id">The specified id.</param>
        /// <returns>The matched data.</returns>
        public virtual T Load(object id)
        {
            return Get(id);
        }

        /// <summary>
        /// Filter data by lambda expression.
        /// </summary>
        /// <param name="expression">The lambda expression that accepts T and return boolean.</param>
        /// <returns>An IQueryable list of data.</returns>
        public virtual IQueryable<T> Query(Expression<Func<T, bool>> expression)
        {
            return Query(expression, string.Empty);
        }

        /// <summary>
        /// Query data by lambda expression.
        /// </summary>
        /// <param name="expression">The lambda expression that accepts T and return boolean.</param>
        /// <param name="includePath">The include path for loading navigation property. 
        /// Multi-Property is allowed. Example: "TestResults;TestResultDiagnosticMessages"</param>
        /// <returns>An IQueryable list of data.</returns>
        public virtual IQueryable<T> Query(Expression<Func<T, bool>> expression, string includePath)
        {
            DbQuery<T> result = null;

            if (!string.IsNullOrEmpty(includePath))
            {
                var includePathArray = includePath.Split(';');

                foreach (var singlePath in includePathArray)
                {
                    if (!string.IsNullOrEmpty(singlePath))
                    {
                        result = result == null ? DataContext.Set<T>().Include(singlePath) : result.Include(singlePath.Trim());
                    }
                }
            }
            else
            {
                result = DataContext.Set<T>();
            }

            return expression == null ? result.AsQueryable() : result.Where(expression).AsQueryable();
        }

        /// <summary>
        /// Get all data. You also can filter the data later since the returned result is a IQueryable collection.
        /// </summary>
        /// <returns>An IQueryable collection of data.</returns>
        public virtual IQueryable<T> GetAll()
        {
            return Query(null);
        }

        /// <summary>
        /// Get all data. You also can filter the data later since the returned result is a IQueryable collection.
        /// </summary>
        /// <param name="expression">The lambda expression that accepts T and return boolean.</param>
        /// <param name="includePath">The include path for loading navigation property. 
        /// Multi-Property is allowed. Example: "TestResults;TestResultDiagnosticMessages"</param>
        /// <returns>An IQueryable list of data.</returns>
        public virtual IQueryable<T> GetAll(string includePath)
        {
            return Query(null, includePath);
        }

        /// <summary>
        /// Get the data's count by using provided lambda expression.
        /// </summary>
        /// <param name="expression">The lambda expression that accepts T and return boolean.</param>
        /// <returns>The data's count.</returns>
        public virtual int GetRowCount(Expression<Func<T, bool>> expression)
        {
            return DataContext.Set<T>().Where(expression).Count();
        }

        /// <summary>
        /// Update existing entity.
        /// </summary>
        /// <param name="entity">The entity will be updated.</param>
        public virtual void Update(T entity)
        {
            DataContext.Entry<T>(entity).State = EntityState.Modified;
        }

        /// <summary>
        /// Update existing entity list.
        /// </summary>
        /// <param name="entities">The entity list will be updated.</param>
        public virtual void Update(IEnumerable<T> entities)
        {
            foreach (var entity in entities)
            {
                Update(entity);
            }
        }

        /// <summary>
        /// Add or update entity.
        /// </summary>
        /// <param name="entity">The entity will be updated if it is existing or added if it cannot be found.</param>
        public virtual void AddOrUpdate(T entity) { }

        /// <summary>
        /// Add or update entity list.
        /// </summary>
        /// <param name="entities">The entity list will be updated if it is existing or added if it cannot be found.</param>
        public virtual void AddOrUpdate(IEnumerable<T> entities)
        {
            foreach (var entity in entities)
            {
                AddOrUpdate(entity);
            }
        }

        /// <summary>
        /// Delete the specified entity.
        /// </summary>
        /// <param name="entity">The entity will be deleted from database.</param>
        public virtual void Delete(T entity)
        {
            DataContext.Entry<T>(entity).State = EntityState.Deleted;
            DataContext.Set<T>().Remove(entity);
        }

        /// <summary>
        /// Delete the specified entity list.
        /// </summary>
        /// <param name="entities">The entity list will be deleted from database.</param>
        public virtual void Delete(IEnumerable<T> entities)
        {
            foreach (var entity in entities)
            {
                Delete(entity);
            }
        }

        /// <summary>
        /// Executes the given DDL/DML command against the database.
        /// 1. Pass raw arguments and use the {0} syntax
        /// 2. Pass DbParameter subclass arguments and use @ParamName syntax.
        /// If you use the first syntax, EF will actually wrap your arguments 
        /// with DbParamater classes, assign them names, and 
        /// replace {0} with the generated parameter name.
        /// </summary>
        /// <param name="sql">The command string.</param>
        /// <param name="parameters">The parameters to apply to the command string.</param>
        public virtual void ExecuteSqlCommand(string sql, params Object[] parameters)
        {
            DataContext.Database.ExecuteSqlCommand(sql, parameters);
        }
    }
}
